import {
  inject,
  getCurrentInstance,
  effectScope,
  reactive,
  computed,
  toRefs,
  watch
} from "vue";
import { addSubscription, triggerSubscription } from "./pubSub";

import { activePinia, setActivePinia, SymbolPinia } from "./rootStore";
import { mergeReactiveObject } from "./utils";

export function defineStore(idOrOptions, setup) {
  let id;
  let options;
  if (typeof idOrOptions === "string") {
    id = idOrOptions;
    options = setup;
  } else {
    options = idOrOptions;
    id = idOrOptions.id;
  }
  // 用户传递的是 setup 函数
  const isSetupStore = typeof setup === "function";

  function useStore() {
    const currentInstance = getCurrentInstance();
    let pinia = currentInstance && inject(SymbolPinia);

    //#region 配合 optionsApi 的  mapActions 使用
    if (pinia) setActivePinia(pinia);
    pinia = activePinia;
    //#endregion

    pinia._a = pinia;

    if (!pinia._s.has(id)) {
      if (isSetupStore) {
        // 用户传递的是 setup 函数
        createSetupStore(id, setup, pinia);
      } else {
        // 用户传递的是一个对象
        createOptionsStore(id, options, pinia);
      }
    }
    const store = pinia._s.get(id);

    return store;
  }

  return useStore;
}

function createSetupStore(id, setup, pinia) {
  let scope;
  const $patch = (partialStateOrMutation) => {
    if (typeof partialStateOrMutation === "function") {
      partialStateOrMutation(store);
    } else {
      mergeReactiveObject(store, partialStateOrMutation);
    }
  };

  const $subscribe = (callback, options) => {
    scope.run(() => watch(pinia.state.value[id], state => { // 监控状态变化
      callback({ type: 'dirct' }, state);
    }, options))
  };
  const actionSubscribes = []
  const partialStore = {
    $patch,
    $reset: null,
    $subscribe,
    $onAction: addSubscription.bind(null, actionSubscribes),
    $dispose: () => {
      scope.stop();
      actionSubscribes.length = 0;
      pinia._s.delete(id);
    }
  };
  const store = reactive(partialStore);

  const setupStore = pinia._e.run(() => {
    scope = effectScope();
    return scope.run(() => setup());
  });

  function wrapAction(name, action) {
    return function () {
      // 触发 actions 的方法时，会执行该函数

      const afterCallbackList = []
      const onErrorCallbackList = []
      function after(callback) {
        afterCallbackList.push(callback)
      }
      function onError(callback) {
        onErrorCallbackList.push(callback)
      }
      triggerSubscription(actionSubscribes, { after, onError, store, name });

      const ret = action.apply(store, arguments);

      if (ret instanceof Promise) {
        return ret.then((value) => {
          triggerSubscription(afterCallbackList, value)
        }).catch(error => {
          triggerSubscription(onErrorCallbackList, error);
          return Promise.reject(error)
        })
      } else {
        triggerSubscription(afterCallbackList, ret)
      }
      // 这里可以处理返回值
      return ret;
    };
  }

  for (const key in setupStore) {
    const prop = setupStore[key];
    if (typeof prop === "function") {
      //
      /**
       * 修正 this 的指向问题，为了解构 actions 可以正确使用
       * 例如：
       * const { increment } = store;
          const handleClick3 = () => {
            increment();
          };
       */
      setupStore[key] = wrapAction(key, prop);
    }
  }

  Object.defineProperty(store, "$state", {
    get: () => pinia.state.value[id],
    set: (state) => $patch($state => Object.assign($state, state))
  })

  Object.assign(store, setupStore);

  pinia._p.forEach(plugin => {
    typeof plugin === 'function' ? plugin({ store, pinia, app: pinia._a, storeId: id }) : {};
  })
  pinia._s.set(id, store);

  return store;
}

function createOptionsStore(id, options, pinia) {
  const { state, getters, actions } = options;
  function setup() {
    pinia.state.value[id] = state ? state() : {};
    const localState = toRefs(pinia.state.value[id]); // toRefs 解决 getters 作为计算属性更新不了的问题

    return Object.assign(
      localState,
      actions,
      Object.keys(getters || {}).reduce((computedGetters, name) => {
        computedGetters[name] = computed(() => {
          return getters[name].call(store, store);
        });

        return computedGetters;
      }, {})
    );
  }

  const store = createSetupStore(id, setup, pinia);
  store.$reset = () => {
    // 该方法只能在非 setupStore 函数中调用， 例如， defineStore 第二个参数就是传递一个 setupStore 函数时是不能使用的， // 参考写法三就不能了
    const newState = state ? state() : {};
    store.$patch(($state) => {
      Object.assign($state, newState);
    })
  }
  return store;
}

/**
import { defineStore } from "../pinia";

import { reactive, computed, toRefs } from 'vue';

// 写法一
export const useCounterStore = defineStore('counter', {
  // counter 唯一的
  // 定义状态
  state: () => ({
    count: 999,
    fruits: ['1', '3']
  }),
  // 类似计算属性
  getters: {
    doubleCount: (store) => store.count * 2
  },
  // 类似方法,不区分是否是异步了
  actions: {
    increment() {
      this.count++;
    }
  }
});

// 写法二
export const useCounterStore = defineStore({
  id: "counter",
  // counter 唯一的
  // 定义状态
  state: () => ({
    count: 999,
    fruits: ['1', '3']
  }),
  // 类似计算属性
  getters: {
    doubleCount: (store) => store.count * 2
  },
  // 类似方法,不区分是否是异步了
  actions: {
    increment() {
      this.count++;
    }
  }
});

// 写法三
// 这种写法， store 的实例就不能调用 $reset 函数
export const useCounterStore = defineStore('counter', () => {
  const state = reactive({ count: 0, fruits: ['1', '3'] });
  const doubleCount = computed(() => state.count * 2);

  const increment = () => state.count++;

  return {
    ...toRefs(state),
    doubleCount,
    increment,
  }
});

 */